#include "AssimpParser.h"
#include "Texture.h"
#include "SceneGraph.h"

#include "Material.h"
#include "Shader.h"

#include <memory>

AssimpParser::AssimpParser(const aiScene* scene, const std::string& path)
{
	this->scene = scene;
	this->path = path;
}

AssimpParser::~AssimpParser()
{

}

std::shared_ptr<SceneNode> AssimpParser::Parse()
{
	LoadTextures();
	return LoadModel();
}

void AssimpParser::LoadTextures()
{
	if (scene->HasTextures()) return;

	for (unsigned int m=0; m<scene->mNumMaterials; m++)
	{
		aiMaterial* mat = scene->mMaterials[m];
		aiString materialName;
		mat->Get(AI_MATKEY_NAME, materialName);
			
		aiColor3D ambientColor;
		mat->Get(AI_MATKEY_COLOR_AMBIENT, ambientColor);

		aiColor3D diffuseColor;
		mat->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);
			
		aiColor3D specularColor;
		mat->Get(AI_MATKEY_COLOR_SPECULAR, specularColor);

		aiString path;	// filename

		std::shared_ptr<Shader> shader = std::shared_ptr<Shader>(new Shader(Shader::GetShader("MVPShader")));
		std::shared_ptr<Material> material = std::shared_ptr<Material>(new Material(materialName.C_Str(), true, true, shader));

		int texCount = mat->GetTextureCount(aiTextureType_DIFFUSE);
		for(int i=0; i<texCount; i++)
		{
			aiReturn texFound = mat->GetTexture(aiTextureType_DIFFUSE, i, &path);
			if(texFound == AI_SUCCESS)
			{
				std::string pathStr = std::string(path.C_Str());
				std::string basepath = GetBasePath(this->path);
				std::string fileloc = basepath + pathStr;

				std::shared_ptr<Texture> newTexture = std::shared_ptr<Texture>(new Texture(fileloc));
				newTexture->LoadImage();

				shader->AddParameter(Shader::Diffuse, newTexture);
			}
		}
		
		Material::materialsNames.insert(std::pair<int, std::string>(m, materialName.C_Str()));
		Material::materials.insert(std::pair<std::string, std::shared_ptr<Material>>(materialName.C_Str(), material));
	}

}

std::shared_ptr<SceneNode> AssimpParser::LoadModel()
{
	aiNode* root = scene->mRootNode;
	return ParseModel(root, nullptr);
}

std::shared_ptr<SceneNode> AssimpParser::ParseModel(aiNode* node, std::shared_ptr<SceneNode> sceneNode)
{
	std::shared_ptr<SceneNode> newNode = std::shared_ptr<SceneNode>(new SceneNode(std::string(node->mName.C_Str())));
	
	//if sceneNode is null, that means we are starting from root node, if not then we're inside the recursion with its children
	if(sceneNode == nullptr)
	{
		std::weak_ptr<SceneGraph> defaultSceneGraph = SceneGraph::GetDefaultSceneGraph();
		std::shared_ptr<SceneGraph> defaultSceneGraphPtr = std::shared_ptr<SceneGraph>(defaultSceneGraph);

		std::shared_ptr<SceneNode> rootNode = std::shared_ptr<SceneNode>(defaultSceneGraphPtr->GetRoot());
		rootNode->AddChild(newNode);
	}

	//set transformation matrix
	glm::mat4 matrix = glm::mat4(1.0f);
	matrix[0][0] = node->mTransformation.a1;
	matrix[0][1] = node->mTransformation.a2;
	matrix[0][2] = node->mTransformation.a3;
	matrix[0][3] = node->mTransformation.a4;
	
	matrix[1][0] = node->mTransformation.b1;
	matrix[1][1] = node->mTransformation.b2;
	matrix[1][2] = node->mTransformation.b3;
	matrix[1][3] = node->mTransformation.b4;
	
	matrix[2][0] = node->mTransformation.c1;
	matrix[2][1] = node->mTransformation.c2;
	matrix[2][2] = node->mTransformation.c3;
	matrix[2][3] = node->mTransformation.c4;
	
	matrix[3][0] = node->mTransformation.d1;
	matrix[3][1] = node->mTransformation.d2;
	matrix[3][2] = node->mTransformation.d3;
	matrix[3][3] = node->mTransformation.d4;
	newNode->SetModelMatrix(glm::transpose(matrix));


	//create meshes
	for(unsigned int i=0; i<node->mNumMeshes; i++)
	{
		int meshIndex = node->mMeshes[i];
		aiMesh* mesh = scene->mMeshes[meshIndex];

		int materialIndex = mesh->mMaterialIndex;
		std::string meshMaterialName = std::string("");
		if(Material::materialsNames.find(materialIndex) != Material::materialsNames.end())
		{
			meshMaterialName = Material::materialsNames.at(materialIndex);
		}

		std::shared_ptr<SceneMesh> newMesh = std::shared_ptr<SceneMesh>(new SceneMesh(meshMaterialName));
		newNode->AddMesh(newMesh);
		newMesh->SetParent(newNode);

		ParseMesh(newMesh, mesh);
	}

	if(sceneNode != nullptr)
	{
		sceneNode->AddChild(newNode);
	}

	for(unsigned int i=0; i<node->mNumChildren; i++)
	{
		aiNode* child = node->mChildren[i];		
		ParseModel(child, newNode);
	}

	return newNode;
}


void AssimpParser::ParseMesh(std::shared_ptr<SceneMesh> sceneMesh, aiMesh* mesh)
{
	int indexArraySize = mesh->mNumFaces * 3;
	sceneMesh->CreateIndexArray(indexArraySize);

	int count = 0;

	for(unsigned int i=0; i<mesh->mNumFaces; i++)
	{
		aiFace face = mesh->mFaces[i];
		assert(face.mNumIndices == 3);

		for(unsigned int j=0; j<face.mNumIndices; j++)
		{
			sceneMesh->SetIndexArray(count, face.mIndices[j]);
			count++;
		}
	}

	sceneMesh->CreateVertexArray(mesh->mNumVertices);

	count = 0;
	int uvCount = 0;
	for(unsigned int i=0; i<mesh->mNumVertices; i++)
	{
		aiVector3D vertex = mesh->mVertices[i];
		aiVector3D normal = mesh->mNormals[i];
		for(int j=0; j<3; j++)
		{
			if(j == 0) 
			{
				sceneMesh->SetVertexArray(count, vertex.x);
				sceneMesh->SetNormalArray(count, normal.x);
			}
			else if(j == 1) 
			{
				sceneMesh->SetVertexArray(count, vertex.y);
				sceneMesh->SetNormalArray(count, normal.y);
			}
			else if(j == 2) 
			{
				sceneMesh->SetVertexArray(count, vertex.z);
				sceneMesh->SetNormalArray(count, normal.z);
			}
			count++;
		}

		aiVector3D texCoord = mesh->mTextureCoords[0][i];
		for(int j=0; j<2; j++)
		{
			if(j == 0) 
			{
				sceneMesh->SetUVArray(uvCount, texCoord.x);
			}
			else if(j == 1) 
			{
				sceneMesh->SetUVArray(uvCount, texCoord.y);
			}
			uvCount++;
		}
	}

	sceneMesh->GenVBO();
}

std::string AssimpParser::GetBasePath(const std::string& path)
{
	size_t pos = path.find_last_of("\\/");
	return (std::string::npos == pos) ? "" : path.substr(0, pos + 1);
}